﻿using Common;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using static System.Runtime.CompilerServices.RuntimeHelpers;

namespace IP20.Procdure
{
    internal class JLink
    {
        public static Action<string> LogEvent { get; set; }

        // 负责生成脚本内容
        private static string GenerateScriptContent(string binFile, string jLinkSn, string device, string speed,
            string selectedInterface, bool onlyErase, (uint AlignedStartAddr, uint AlignedEndAddr)? eraseRange = null)
        {
            if (onlyErase)
            {
                var range = eraseRange ?? CalculateEraseRange(binFile, 0x00);
                return $"""
                        USB {jLinkSn}
                        Device {device}
                        Speed {speed}
                        SelectInterface {selectedInterface}
                        Erase 0x{range.AlignedStartAddr:X8} 0x{range.AlignedEndAddr:X8}
                        Exit
                        """;
            }
            else
            {
                return $"""
                        USB {jLinkSn}
                        Device {device}
                        Speed {speed}
                        SelectInterface {selectedInterface}
                        LoadFile {binFile} 0x00000000 reset
                        VerifyBin {binFile} 0x00000000
                        Exit
                        """;
            }
        }

        private static string GenerateScriptFile(string binFile, string jLinkSn, string device, string speed,
            string selectedInterface, bool onlyErase, (uint AlignedStartAddr, uint AlignedEndAddr)? eraseRange = null)
        {
            // 生成脚本文件路径
            string scriptName = $"{Path.GetFileNameWithoutExtension(binFile)}_{jLinkSn}_{(onlyErase ? "erase" : "program")}.jlink";
            string scriptPath = Path.Combine(ScriptDirectory, scriptName);

            // 生成脚本内容
            string scriptContent = GenerateScriptContent(binFile, jLinkSn, device, speed, selectedInterface, onlyErase);

            // 写入文件
            File.WriteAllText(scriptPath, scriptContent);
            return scriptPath;
        }

        private static void LogError(string output, string error, string operation)
        {
            LogEvent?.Invoke($"{operation}失败");

            // 提取并去重错误行
            var errorLines = output.Split('\n')
                .Concat(error.Split('\n'))
                .Where(line => line.Contains("Error") || line.Contains("Failed"))
                .Distinct();

            foreach (var line in errorLines)
            {
                LogEvent?.Invoke(line.Trim());
            }
        }

        private static readonly string ScriptDirectory = Path.Combine(Environment.CurrentDirectory, "JLink_script");
        private static readonly string LogDirectory = Path.Combine(Environment.CurrentDirectory, "JLink_logs");

        static JLink()
        {
            Directory.CreateDirectory(ScriptDirectory);
            Directory.CreateDirectory(LogDirectory);
        }

        // 负责执行 J-Link 命令
        private static bool ExecuteJLinkCommand(string jLinkExePath, string scriptPath, string operate)
        {
            var processInfo = new ProcessStartInfo
            {
                FileName = jLinkExePath,
                Arguments = $"-CommanderScript \"{scriptPath}\"",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                CreateNoWindow = true
            };

            using var process = new Process();
            process.StartInfo = processInfo;
            process.Start();
            string output = process.StandardOutput.ReadToEnd();
            string error = process.StandardError.ReadToEnd();
            process.WaitForExit();

            if (process.ExitCode != 0 || !string.IsNullOrEmpty(error) || output.Contains("Error"))
            {
                LogError(output, error, operate);
                // 写入文件
                File.WriteAllText($"{LogDirectory}\\{DateTime.Now:yyyy-MM-dd HH mm ss fff}.txt", output);
                return false;
            }

            LogEvent?.Invoke($"{operate}成功");
            return true;
        }

        private static (uint AlignedStartAddr, uint AlignedEndAddr) CalculateEraseRange(
           string binPath,
           uint baseAddress = 0x00000000,
           uint sectorSize = 1024,
           byte fillValue = 0xFF)
        {
            // 1. 读取二进制文件
            byte[] fileBytes = File.ReadAllBytes(binPath);

            // 2. 查找有效数据范围（跳过填充值）
            uint dataStart = baseAddress;
            uint dataEnd = baseAddress;

            // 查找第一个非填充值的地址
            for (int i = 0; i < fileBytes.Length; i++)
            {
                if (fileBytes[i] != fillValue)
                {
                    dataStart = baseAddress + (uint)i;
                    break;
                }
            }

            // 查找最后一个非填充值的地址
            for (int i = fileBytes.Length - 1; i >= 0; i--)
            {
                if (fileBytes[i] != fillValue)
                {
                    dataEnd = baseAddress + (uint)i;
                    break;
                }
            }

            // 3. 按扇区对齐地址
            uint alignedStart = dataStart / sectorSize * sectorSize;
            uint alignedEnd = ((dataEnd + sectorSize - 1) / sectorSize) * sectorSize;

            return (alignedStart, alignedEnd);
        }

        public static bool JLink_Commander(string jLinkExePath, string binFile, string jLinkSn,
        string device = "R7FA4T1BB", string speed = "1000", string selectedInterface = "SWD", bool onlyErase = false)
        {
            string operate = onlyErase ? "擦除" : "烧录";
            try
            {
                // 参数校验
                if (string.IsNullOrEmpty(jLinkExePath) || !File.Exists(jLinkExePath))
                    throw new ArgumentException("J-Link 路径无效或文件不存在");

                if (string.IsNullOrEmpty(binFile) || !File.Exists(binFile))
                    throw new ArgumentException("二进制文件路径无效或文件不存在");

                // 生成脚本并执行
                string scriptPath = GenerateScriptFile(binFile, jLinkSn, device, speed, selectedInterface, onlyErase);
                return ExecuteJLinkCommand(jLinkExePath, scriptPath, onlyErase ? "擦除" : "烧录");
            }
            catch (ArgumentException ex)
            {
                LogEvent?.Invoke($"参数错误: {ex.Message}");
                return false;
            }
            catch (IOException ex)
            {
                LogEvent?.Invoke($"文件操作失败: {ex.Message}");
                return false;
            }
            catch (Exception ex)
            {
                LogEvent?.Invoke($"未知错误: {ex.Message}");
                return false;
            }
        }

        public static bool WriteJFlash(string flashFilePath, string jFlashExePath, string jLinkSn)
        {
            string cmd = $"-usb{jLinkSn} -openprj\"{flashFilePath}\" -auto -exit";

            Process jFlashProcess = new Process();
            jFlashProcess.StartInfo.FileName = jFlashExePath;
            jFlashProcess.StartInfo.Arguments = cmd;
            jFlashProcess.StartInfo.UseShellExecute = false;
            jFlashProcess.StartInfo.RedirectStandardOutput = true;
            jFlashProcess.StartInfo.RedirectStandardError = true;
            jFlashProcess.StartInfo.CreateNoWindow = true;

            try
            {
                // 启动进程并等待完成
                jFlashProcess.Start();
                jFlashProcess.WaitForExit(5000);

                // 检查J-Flash的退出代码
                int exitCode = jFlashProcess.ExitCode;
                if (exitCode == 0)
                {
                    LogEvent?.Invoke("JFlash烧写成功");
                    return true;
                }
                else
                {
                    LogEvent?.Invoke($"JFlash烧写失败，退出代码: {exitCode}");
                    throw new Exception($"JFlash烧写失败，退出代码: {exitCode}");
                }
            }
            catch (Exception ex)
            {
                LogEvent?.Invoke($"发生错误: {ex.Message}");
                return false;
            }
            finally
            {
                // 关闭进程和释放资源
                jFlashProcess.Close();
                jFlashProcess.Dispose();
            }
        }

        public static bool WriteJFlash(string flashFilePath, string jFlashExePath)
        {
            string cmd = $"-openprj\"{flashFilePath}\" -auto -exit";

            Process jFlashProcess = new Process();
            jFlashProcess.StartInfo.FileName = jFlashExePath;
            jFlashProcess.StartInfo.Arguments = cmd;
            jFlashProcess.StartInfo.UseShellExecute = false;
            jFlashProcess.StartInfo.RedirectStandardOutput = true;
            jFlashProcess.StartInfo.RedirectStandardError = true;
            jFlashProcess.StartInfo.CreateNoWindow = true;

            try
            {
                // 启动进程并等待完成
                jFlashProcess.Start();
                jFlashProcess.WaitForExit();

                // 检查J-Flash的退出代码
                int exitCode = jFlashProcess.ExitCode;
                if (exitCode == 0)
                {
                    LogEvent?.Invoke("JFlash烧写成功");
                    return true;
                }
                else
                {
                    LogEvent?.Invoke($"JFlash烧写失败，退出代码: {exitCode}");
                    throw new Exception($"JFlash烧写失败，退出代码: {exitCode}");
                }
            }
            catch (Exception ex)
            {
                LogEvent?.Invoke($"发生错误: {ex.Message}");
                return false;
            }
            finally
            {
                // 关闭进程和释放资源
                jFlashProcess.Close();
                jFlashProcess.Dispose();
            }
        }
    }
}